package com.hulman.oms.service;

import com.hulman.oms.annotation.TaskListener;
import com.hulman.oms.annotation.TaskTimeout;
import com.hulman.oms.bean.*;
import com.hulman.oms.dao.DeviceDao;
import com.hulman.oms.dao.FaultDao;
import com.hulman.oms.exception.IoTException;
import com.hulman.oms.util.DateUtil;
import com.hulman.oms.util.OrderUtil;
import com.hulman.oms.util.SubjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: maxwellens
 */
@Service
@Slf4j
@TaskListener
public class FaultService
{
    private static final String PROCESS_KEY = "fault";

    /**
     * 审核通过
     */
    private static final Integer AUDIT_OK = 1;

    /**
     * 审核不通过
     */
    private static final Integer AUDIT_NG = 2;

    @Autowired
    private FaultDao faultDao;
    @Autowired
    private UserService userService;
    @Autowired
    private TeamService teamService;
    @Autowired
    private ProcInstService procInstService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private MsgService msgService;
    @Autowired
    private DeviceAlarmService deviceAlarmService;
    @Autowired
    private DeviceDao deviceDao;


    public List<Fault> findFaults(Map<String, Object> map)
    {
        return faultDao.findFaults(map);
    }

    public Integer findFaultsCount(Map<String, Object> map)
    {
        return faultDao.findFaultsCount(map);
    }

    public Result findFaultsResult(Map<String, Object> map)
    {
        Integer page = (Integer) map.get("page");
        Integer limit = (Integer) map.get("limit");
        if (page != null && limit != null)
        {
            map.put("start", (page - 1) * limit);
            map.put("length", limit);
        }
        int count = faultDao.findFaultsCount(map);
        List<Fault> data = faultDao.findFaults(map);
        return new Result(data, count);
    }

    public Fault findFaultById(Integer id)
    {
        return faultDao.findFaultById(id);
    }

    public void saveFault(Fault fault)
    {
        if (fault.getId() != null && fault.getId() != 0)
        {
            faultDao.updateFault(fault);
        } else
        {
            faultDao.insertFault(fault);
        }
    }

    public void deleteFaultById(Integer id)
    {
        faultDao.deleteFaultById(id);
    }

    public void deleteFaults(int[] ids)
    {
        faultDao.deleteFaults(ids);
    }

    /**
     * 上报缺陷
     *
     * @param content
     * @param deviceId
     * @param deviceName
     * @param faultHandle
     * @param faultImgs
     */
    @Transactional(rollbackFor = Exception.class)
    public void report(Integer source, Integer alarmId, String content, Integer deviceId, String deviceName, String deviceLocation, Integer teamId, Integer faultHandle, String faultImgs)
    {
        //生成工单
        User user = SubjectUtil.getUser();
        if (user == null)
        {
            throw new IoTException("用户未登陆");
        }
        ProcInst procInst = procInstService.startProcess(PROCESS_KEY);
        Fault fault = new Fault();
        fault.setNo(OrderUtil.generate("QX"));
        fault.setProcInstId(procInst.getId());
        if (source == null)
        {
            source = Fault.SOURCE_MANUAL;
        }
        fault.setSource(source);
        fault.setAlarmId(alarmId);
        fault.setContent(content);
        fault.setDeviceId(deviceId);
        fault.setDeviceName(deviceName);
        fault.setDeviceLocation(deviceLocation);
        fault.setFaultHandle(faultHandle);
        fault.setFaultImgs(faultImgs);
        fault.setReportById(user.getId());
        fault.setReportByName(user.getName());
        fault.setAuditTimes(0);
        fault.setReportTime(new Date());
        Team team = teamService.findTeamById(teamId);
        fault.setTeamId(team.getId());
        fault.setTeamName(team.getName());
        fault.setTeamLeaderId(team.getLeaderId());
        fault.setTeamLeaderName(team.getLeaderName());
        fault.setAuditById(0);

        //将设备状态置为维修中
        Device device = deviceDao.findDeviceById(deviceId);
        if (device != null)
        {
            device.setState(2);
            deviceDao.updateDevice(device);
        }

        Task thisTask = new Task();
        thisTask.setProcInstId(fault.getProcInstId());
        thisTask.setOwnerId(user.getId());
        thisTask.setOwnerName(user.getName());
        thisTask.setName(procInst.getStateName());
        thisTask.setTag(PROCESS_KEY);
        taskService.createAndCompleteTask(thisTask);

        //根据上报方式决定流向:
        //当场处置
        if (fault.getFaultHandle() == Fault.FAULT_HANDLE_IMMEDIATE)
        {
            fault.setState(Fault.STATE_AUDITING);
            faultDao.insertFault(fault);
            procInst = procInstService.to(procInst.getId(), 3);
            //给巡检负责人发任务
            User patrolDirector = userService.findPatrolDirector(fault.getArea());
            if (patrolDirector == null)
            {
                throw new IoTException("未配置巡检负责人");
            }
            fault.setAuditById(patrolDirector.getId());
            fault.setAuditByName(patrolDirector.getName());
            faultDao.updateFault(fault);
            Task task = new Task();
            task.setProcInstId(procInst.getId());
            task.setOwnerId(patrolDirector.getId());
            task.setOwnerName(patrolDirector.getName());
            task.setName(procInst.getStateName());
            task.setTag(PROCESS_KEY);
            task.setFormKey(fault.getId().toString());
            task.setDueTime(DateUtil.addHours(new Date(), 4));//4小时有效期，到期触发超时事件
            task.addExtra("工单号", fault.getNo());
            task.addExtra("缺陷描述", fault.getContent());
            task.addExtra("上报人", fault.getReportByName());
            task.addExtra("上报时间", DateUtil.toDate(fault.getReportTime()));
            taskService.addTask(task);
            task.setPath("/pages/fault/detail?taskId=" + task.getId());
            task.setPage("/fault-info.html?taskId=" + task.getId());
            taskService.saveTask(task);
            msgService.sendMsg(task.toMsg());
        }
        //上报处置
        else if (fault.getFaultHandle() == Fault.FAULT_HANDLE_REPORT)
        {
            fault.setState(Fault.STATE_HANDLING);
            faultDao.insertFault(fault);
            procInst = procInstService.next(procInst.getId());
            //给值班长发任务
            User shiftLeader = userService.findShiftLeader();
            if (shiftLeader == null)
            {
                throw new IoTException("当前无值班长");
            }
            Task task = new Task();
            task.setProcInstId(procInst.getId());
            task.setOwnerId(shiftLeader.getId());
            task.setOwnerName(shiftLeader.getName());
            task.setName(procInst.getStateName());
            task.setTag(PROCESS_KEY);
            task.setFormKey(fault.getId().toString());
            task.addExtra("工单号", fault.getNo());
            task.addExtra("缺陷描述", fault.getContent());
            task.addExtra("设施设备", fault.getDeviceName());
            task.addExtra("上报人", fault.getReportByName());
            task.addExtra("上报时间", DateUtil.toDate(fault.getReportTime()));
            taskService.addTask(task);
            task.setPath("/pages/fault/detail?taskId=" + task.getId());
            task.setPage("/fault-info.html?taskId=" + task.getId());
            taskService.saveTask(task);
            msgService.sendMsg(task.toMsg());
        }
        //给巡检班长发通知
        Msg msg = new Msg();
        msg.setType(Msg.TYPE_OM);
        msg.setOwnerId(team.getLeaderId());
        msg.setOwnerName(team.getLeaderName());
        msg.setCreateTime(new Date());
        msg.setState(Msg.STATE_UNREAD);
        msg.setTitle("上报新的缺陷");
        msg.addItem("缺陷描述", fault.getContent());
        msg.addItem("设施设备", fault.getDeviceName());
        msg.addItem("上报人", fault.getReportByName());
        msg.addItem("上报时间", DateUtil.toDate(fault.getReportTime()));
        msgService.sendMsg(msg);

        //自动确认设备告警
        if (fault.getAlarmId() != null)
        {
            deviceAlarmService.confirmDeviceAlarm(fault.getAlarmId());
        }
    }

    /**
     * 维修处置
     *
     * @param id
     * @param level
     * @param repairHandle
     */
    @Transactional(rollbackFor = Exception.class)
    public void repair(Integer taskId, Integer id, Integer level, Integer repairHandle)
    {
        //修改工单
        Fault fault = faultDao.findFaultById(id);
        fault.setLevel(level);
        fault.setRepairHandle(repairHandle);
        fault.setState(Fault.STATE_TO_REPAIR);
        faultDao.updateFault(fault);
        //完成本次任务
        taskService.createAndCompleteTask(taskId);
    }

    /**
     * 完成维修
     *
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    public void complete(Integer id)
    {
        //修改工单
        Fault fault = faultDao.findFaultById(id);
        //指定常务经理为审核人
        User standingManager = userService.findStandingManager();
        if (standingManager == null)
        {
            throw new IoTException("未配置常务经理");
        }
        fault.setAuditById(standingManager.getId());
        fault.setAuditByName(standingManager.getName());
        fault.setState(Fault.STATE_AUDITING);
        faultDao.updateFault(fault);
        ProcInst procInst = procInstService.next(fault.getProcInstId());
        //给常务经理发送审核任务
        if (standingManager == null)
        {
            throw new IoTException("未配置常务经理");
        }
        Task task = new Task();
        task.setProcInstId(procInst.getId());
        task.setOwnerId(standingManager.getId());
        task.setOwnerName(standingManager.getName());
        task.setName(procInst.getStateName());
        task.setTag(PROCESS_KEY);
        task.setFormKey(fault.getId().toString());
        task.addExtra("工单号", fault.getNo());
        task.addExtra("缺陷描述", fault.getContent());
        task.addExtra("设施设备", fault.getDeviceName());
        taskService.addTask(task);
        task.setPath("/pages/fault/detail?taskId=" + task.getId());
        task.setPage("/fault-info.html?taskId=" + task.getId());
        taskService.saveTask(task);
        msgService.sendMsg(task.toMsg());
    }

    /**
     * 审核缺陷
     *
     * @param taskId
     * @param id
     * @param level
     * @param auditResult
     * @param auditComment
     * @param auditLocation
     * @param auditImgs
     */
    @Transactional(rollbackFor = Exception.class)
    public void audit(Integer taskId, Integer id, Integer level, Integer auditResult, String auditComment, String auditLocation, String auditImgs)
    {
        Fault fault = faultDao.findFaultById(id);
        if (level != null)
        {
            fault.setLevel(level);
        }
        fault.setAuditResult(auditResult);
        fault.setAuditComment(auditComment);
        fault.setAuditLocation(auditLocation);
        fault.setAuditImgs(auditImgs);
        fault.setAuditTime(new Date());
        fault.setAuditTimes(fault.getAuditTimes() + 1);
        //完成本次任务
        taskService.createAndCompleteTask(taskId);
        if (fault.getAuditResult() == AUDIT_OK)
        {
            fault.setState(Fault.STATE_DOCUMENTED);
            faultDao.updateFault(fault);
            //自动处理设备告警
            if (fault.getAlarmId() != null)
            {
                deviceAlarmService.handleDeviceAlarm(fault.getAlarmId());
            }

            //将设备状态重新置为运行中
            Integer deviceId = fault.getDeviceId();
            if (deviceId != null)
            {
                Device device = deviceDao.findDeviceById(deviceId);
                if (device != null)
                {
                    device.setState(1);
                    deviceDao.updateDevice(device);
                }
            }

            procInstService.next(fault.getProcInstId());
            //通知项目经理和常务经理
            Msg msg = new Msg();
            msg.setTitle("缺陷审核归档");
            msg.addItem("缺陷描述", fault.getContent());
            msg.addItem("审核结果", fault.getAuditResultText());
            msg.addItem("审核意见", fault.getAuditComment());
            msg.addItem("审核时间", DateUtil.toDate(fault.getAuditTime()));
            notifyManager(msg);
        } else if (auditResult == AUDIT_NG)
        {
            Integer ownerId = 0;
            String ownerName = "";
            //当场处置
            if (fault.getFaultHandle() == 1)
            {
                terminate(id);
                Msg msg = new Msg();
                msg.setType(Msg.TYPE_OM);
                msg.setTitle("审核不通过");
                msg.addItem("缺陷设备", fault.getDeviceName());
                msg.addItem("故障描述", fault.getContent());
                msg.addItem("审核意见", fault.getAuditComment());
                msg.addItem("处理方式", "工单撤销，重新提交");
                msg.setOwnerId(fault.getReportById());
                msg.setOwnerName(fault.getReportByName());
                msgService.sendMsg(msg);
            }
            //上报处置
            else if (fault.getFaultHandle() == 2)
            {
                procInstService.to(fault.getProcInstId(), 2);
                User shiftLeader = userService.findShiftLeader();
                ownerId = shiftLeader.getId();
                ownerName = shiftLeader.getName();
                fault.setState(Fault.STATE_HANDLING);
                faultDao.updateFault(fault);
                //创建新的任务
                Task task = new Task();
                task.setProcInstId(fault.getProcInstId());
                task.setOwnerId(ownerId);
                task.setOwnerName(ownerName);
                task.setName("审核不通过");
                task.setTag(PROCESS_KEY);
                task.setFormKey(fault.getId().toString());
                task.addExtra("缺陷设备", fault.getDeviceName());
                task.addExtra("故障描述", fault.getContent());
                task.addExtra("审核意见", fault.getAuditComment());
                taskService.addTask(task);
                task.setPath("/pages/fault/detail?taskId=" + task.getId());
                taskService.saveTask(task);
                msgService.sendMsg(task.toMsg());
            }
        }
    }

    /**
     * 撤销缺陷
     *
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    public void terminate(Integer id)
    {
        //修改工单
        Fault fault = faultDao.findFaultById(id);
        fault.setState(Fault.STATE_TERMINATED);
        faultDao.updateFault(fault);
        //增加撤销流程信息
        User user = SubjectUtil.getUser();
        Task task = new Task();
        task.setProcInstId(fault.getProcInstId());
        task.setOwnerId(user.getId());
        task.setOwnerName(user.getName());
        task.setName("撤销工单");
        task.setTag(PROCESS_KEY);
        taskService.createAndCompleteTask(task);
        //终止流程
        procInstService.terminate(fault.getProcInstId());
    }

    /**
     * 审核缺陷超时
     *
     * @param task
     */
    @Transactional(rollbackFor = Exception.class)
    @TaskTimeout(name = "审核缺陷", tag = PROCESS_KEY)
    public void auditTimeout(Task task)
    {
        log.info("审核缺陷超时,task:{}", task.toString());
        Integer id = Integer.parseInt(task.getFormKey());
        Fault fault = faultDao.findFaultById(id);
        fault.setState(Fault.STATE_DOCUMENTED);
        faultDao.updateFault(fault);
        //完成本次任务
        taskService.uncompleteTask(task.getId());
        procInstService.next(fault.getProcInstId());
        //通知项目经理和常务经理
        Msg msg = new Msg();
        msg.setTitle("审核缺陷超时");
        msg.addItem("备注", "审核缺陷超时4小时未处理，已自动归档");
        notifyManager(msg);
    }

    /**
     * 维修处置超时
     *
     * @param task
     */
    @Transactional(rollbackFor = Exception.class)
    @TaskTimeout(name = "维修处置", tag = PROCESS_KEY)
    public void repairTimeout(Task task)
    {
        log.info("维修处置超时,task:{}", task.toString());
        //通知项目经理和常务经理
        Msg msg = new Msg();
        msg.setTitle("维修处置超时");
        msg.addItem("备注", "维修处置超时4小时未处理自动归档");
        notifyManager(msg);
    }

    private void notifyManager(Msg msg)
    {
        log.info("notifyManager:{}", msg);
        msg.setType(Msg.TYPE_OM);
        msg.setCreateTime(new Date());
        msg.setState(Msg.STATE_UNREAD);
        //常务经理
        User standingManager = userService.findStandingManager();
        //项目经理
        User projectManager = userService.findProjectManager();
        if (standingManager != null)
        {
            msg.setOwnerId(standingManager.getId());
            msg.setOwnerName(standingManager.getName());
            log.info("standingManager:{}", msg);
            msgService.sendMsg(msg);
        }
        if (projectManager != null)
        {
            msg.setOwnerId(projectManager.getId());
            msg.setOwnerName(projectManager.getName());
            log.info("standingManager:{}", msg);
            msgService.sendMsg(msg);
        }
    }

    public List<Fault> findTodayFaults()
    {
        return faultDao.findTodayFaults();
    }

    public List<Fault> findRecentFaults(Integer count)
    {
        return faultDao.findRecentFaults(count);
    }

    public Integer findTodayFaultsCount(Integer tunnelId)
    {
        return faultDao.findTodayFaultsCount(tunnelId);
    }

    public Integer findTodayAutoFaultsCount(Integer tunnelId)
    {
        return faultDao.findTodayAutoFaultsCount(tunnelId);
    }

    public Integer findTodayManualFaultsCount(Integer tunnelId)
    {
        return faultDao.findTodayManualFaultsCount(tunnelId);
    }

    public Integer findTodayFaultsTotalCount()
    {
        return faultDao.findTodayFaultsTotalCount();
    }

    /**
     * 各系统未处理缺陷
     *
     * @return
     */
    public List<NameValue> statePendingCountBySystem()
    {
        return faultDao.statePendingCountBySystem();
    }

    public List<NameValue> statDailyFaults(Integer days)
    {
        return faultDao.statDailyFaults(days);
    }

    public String getFaultContents(Date date)
    {
        Map<String, Object> map = new HashMap<>();
        map.put("startDate", DateUtil.toDate(date));
        map.put("endDate", DateUtil.toDate(date));
        List<Fault> faults = findFaults(map);
        StringBuilder contentBuilder = new StringBuilder();
        for (int i = 0; i < faults.size(); i++)
        {
            Fault fault = faults.get(i);
            contentBuilder.append(fault.getContent() + "\n");
        }
        return contentBuilder.toString();
    }
}
